#ifdef XG_LINUX

#include <netdb.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/statfs.h>
#include <sys/socket.h>
#include <sys/sysinfo.h>

#define ioctlsocket ioctl

#else

#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#endif

#include <WINSOCK2.h>

#ifdef _MSC_VER
#pragma comment(lib, "WS2_32.lib")
#endif

#endif

#include "../netx.h"

void SocketSetup()
{
#ifdef XG_LINUX
	signal(SIGPIPE, SIG_IGN);
#else
	WSADATA data; WSAStartup(MAKEWORD(2, 2), &data);
#endif
}

BOOL IsSocketTimeout()
{
#ifdef XG_LINUX
	return errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR;
#else
	return WSAGetLastError() == WSAETIMEDOUT;
#endif
}

void SocketClose(SOCKET sock)
{
	if (IsSocketClosed(sock)) return;

#ifdef XG_LINUX
	close(sock);
#else
	closesocket(sock);
#endif
}

BOOL IsSocketClosed(SOCKET sock)
{
	return sock == INVALID_SOCKET || sock < 0;
}

SOCKET SocketConnect(const char* ip, int port)
{
	struct sockaddr_in addr;
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

	if (IsSocketClosed(sock)) return INVALID_SOCKET;

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	if (connect(sock, (struct sockaddr*)(&addr), sizeof(addr)) == 0)
	{
		SocketSetSendTimeout(sock, SOCKECT_SENDTIMEOUT);
		SocketSetRecvTimeout(sock, SOCKECT_RECVTIMEOUT);

		return sock;
	}

	SocketClose(sock);

	return INVALID_SOCKET;
}

SOCKET SocketConnectTimeout(const char* ip, int port, int timeout)
{
	u_long mode = 1;
	struct sockaddr_in addr;
	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

	if (IsSocketClosed(sock)) return INVALID_SOCKET;

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	ioctlsocket(sock, FIONBIO, &mode); mode = 0;

	if (connect(sock, (struct sockaddr*)(&addr), sizeof(addr)) == 0)
	{
		SocketSetSendTimeout(sock, SOCKECT_SENDTIMEOUT);
		SocketSetRecvTimeout(sock, SOCKECT_RECVTIMEOUT);
		ioctlsocket(sock, FIONBIO, &mode);

		return sock;
	}

#ifdef XG_LINUX
	struct epoll_event ev;
	struct epoll_event evs;
	HANDLE handle = epoll_create(1);
	
	if (handle < 0)
	{
		SocketClose(sock);
	
		return INVALID_SOCKET;
	}
	
	memset(&ev, 0, sizeof(ev));
	
	ev.events = EPOLLOUT | EPOLLERR | EPOLLHUP;
	
	epoll_ctl(handle, EPOLL_CTL_ADD, sock, &ev);
	
	if (epoll_wait(handle, &evs, 1, timeout) > 0)
	{
		if (evs.events & EPOLLOUT)
		{
			int res = XG_ERROR;
			socklen_t len = sizeof(res);
	
			getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)(&res), &len);
			ioctlsocket(sock, FIONBIO, &mode);
	
			if (res == 0)
			{
				SocketSetSendTimeout(sock, SOCKECT_SENDTIMEOUT);
				SocketSetRecvTimeout(sock, SOCKECT_RECVTIMEOUT);
				Close(handle);
	
				return sock;
			}
		}
	}

	Close(handle);
#else
	struct timeval tv;

	fd_set ws;
	FD_ZERO(&ws);
	FD_SET(sock, &ws);

	tv.tv_sec = timeout / 1000;
	tv.tv_usec = timeout % 1000 * 1000;

	if (select(sock + 1, NULL, &ws, NULL, &tv) > 0)
	{
		int res = XG_ERROR;
		int len = sizeof(res);
	
		getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)(&res), &len);
		ioctlsocket(sock, FIONBIO, &mode);
	
		if (res == 0)
		{
			SocketSetSendTimeout(sock, SOCKECT_SENDTIMEOUT);
			SocketSetRecvTimeout(sock, SOCKECT_RECVTIMEOUT);

			return sock;
		}
	}
#endif

	SocketClose(sock);
	
	return INVALID_SOCKET;
}

BOOL GetSocketAddress(SOCKET sock, char* address)
{
	struct sockaddr_in addr;
	socklen_t len = sizeof(addr);

	if (getpeername(sock, (struct sockaddr*)(&addr), &len) == 0)
	{
#ifdef XG_LINUX
		char buffer[32] = {0};
		const char* host = inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
#else
		const char* host = inet_ntoa(addr.sin_addr);
#endif
		if (host)
		{
			snprintf(address, 24, "%s:%d", host, (int)(ntohs(addr.sin_port)));

			return TRUE;
		}
	}

	strcpy(address, "UNKNOWN ADDRESS");

	return FALSE;
}

SOCKET ServerSocketAccept(SOCKET svr, char* address)
{
	SOCKET sock = INVALID_SOCKET;

	if (address)
	{
		struct sockaddr_in addr;
		socklen_t len = sizeof(addr);

		sock = accept(svr, (struct sockaddr*)(&addr), &len);

		if (IsSocketClosed(sock)) return INVALID_SOCKET;

#ifdef XG_LINUX
		char buffer[32] = {0};
		const char* host = inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
#else
		const char* host = inet_ntoa(addr.sin_addr);
#endif
		if (host)
		{
			snprintf(address, 24, "%s:%d", host, (int)(ntohs(addr.sin_port)));
		}
		else
		{
			strcpy(address, "UNKNOWN ADDRESS");
		}
	}
	else
	{
		sock = accept(svr, NULL, NULL);

		if (IsSocketClosed(sock)) return INVALID_SOCKET;
	}

	return sock;
}

const char* GetHostAddress(const char* host, char* ip)
{
#ifdef XG_LINUX
	struct addrinfo tmp;
	struct addrinfo* res;
	struct sockaddr_in* addr;

	memset(&tmp, 0, sizeof(tmp));

	tmp.ai_family = AF_INET;
	tmp.ai_flags = AI_PASSIVE;
	tmp.ai_socktype = SOCK_STREAM;

	if (getaddrinfo(host, NULL, &tmp, &res) < 0) return NULL;

	addr = (struct sockaddr_in*)(res->ai_addr);
	ip = (char*)(inet_ntop(AF_INET, &addr->sin_addr, ip, 24));

	freeaddrinfo(res);
#else
	struct hostent* entry = gethostbyname(host);

	if (entry == NULL) return NULL;

	sprintf(ip, "%d.%d.%d.%d",
		(entry->h_addr_list[0][0] & 0x00ff),
		(entry->h_addr_list[0][1] & 0x00ff),
		(entry->h_addr_list[0][2] & 0x00ff),
		(entry->h_addr_list[0][3] & 0x00ff));
#endif
	return ip;
}

SOCKET CreateServerSocket(const char* ip, int port, int backlog)
{
	int val = 1;
	struct sockaddr_in addr;
	SOCKET sock = INVALID_SOCKET;

	sock = socket(AF_INET, SOCK_STREAM, 0);

	if (IsSocketClosed(sock)) return INVALID_SOCKET;

	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)(&val), sizeof(val));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);

	if (bind(sock, (const struct sockaddr*)(&addr), sizeof(addr)) == 0 && listen(sock, backlog) == 0) return sock;

	SocketClose(sock);

	return INVALID_SOCKET;
}

int SocketPeek(SOCKET sock, void* data, int size)
{
	int res = recv(sock, data, size, MSG_PEEK);

	if (res > 0) return res;

	if (res == 0) return XG_NETCLOSE;

	if (IsSocketTimeout()) return 0;

	return XG_NETERR;
}

int SocketRead(SOCKET sock, void* data, int size)
{
	return SocketReadEx(sock, data, size, TRUE);
}

int SocketReadEx(SOCKET sock, void* data, int size, BOOL completed)
{
	char* str = (char*)(data);

	if (completed)
	{
		int res = 0;
		int times = 0;
		int readed = 0;

		while (readed < size)
		{
			res = recv(sock, str + readed, size - readed, 0);

			if (res > 0)
			{
				if (res > SOCKET_TIMEOUT_LIMITSIZE)
				{
					times = 0;
				}
				else
				{
					if (++times > SOCKET_TIMEOUT_REDOTIMES) return XG_TIMEOUT;
				}

				readed += res;
			}
			else if (res == 0)
			{
				return XG_NETCLOSE;
			}
			else
			{
				if (IsSocketTimeout())
				{
					if (++times > SOCKET_TIMEOUT_REDOTIMES) return XG_TIMEOUT;
				}
				else
				{
					return XG_NETERR;
				}
			}
		}

		return readed;
	}
	else
	{
		int res = recv(sock, str, size, 0);

		if (res > 0) return res;

		if (res == 0) return XG_NETCLOSE;

		if (IsSocketTimeout()) return 0;

		return XG_NETERR;
	}
}

int SocketWrite(SOCKET sock, const void* data, int size)
{
	return SocketWriteEx(sock, data, size, TRUE);
}

int SocketWriteEx(SOCKET sock, const void* data, int size, BOOL completed)
{
	const char* str = (const char*)(data);

	if (completed)
	{
		int res = 0;
		int times = 0;
		int writed = 0;

		while (writed < size)
		{
			res = send(sock, str + writed, size - writed, 0);

			if (res > 0)
			{
				if (res > SOCKET_TIMEOUT_LIMITSIZE)
				{
					times = 0;
				}
				else
				{
					if (++times > SOCKET_TIMEOUT_REDOTIMES) return XG_TIMEOUT;
				}
	
				writed += res;
			}
			else
			{
				if (IsSocketTimeout())
				{
					if (++times > SOCKET_TIMEOUT_REDOTIMES) return XG_TIMEOUT;
				}
				else
				{
					return XG_NETERR;
				}
			}
		}
		
		return writed;
	}
	else
	{
		int res = send(sock, str, size, 0);

		if (res > 0) return res;

		if (IsSocketTimeout()) return 0;

		return XG_NETERR;
	}
}

BOOL SocketSetSendTimeout(SOCKET sock, int ms)
{
#ifdef XG_LINUX
	struct timeval tv;

	tv.tv_sec = ms / 1000;
	tv.tv_usec = ms % 1000 * 1000;

	return setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)(&tv), sizeof(tv)) == 0;
#else
	return setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)(&ms), sizeof(ms)) == 0;
#endif
}

BOOL SocketSetRecvTimeout(SOCKET sock, int ms)
{
#ifdef XG_LINUX
	struct timeval tv;

	tv.tv_sec = ms / 1000;
	tv.tv_usec = ms % 1000 * 1000;

	return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)(&tv), sizeof(tv)) == 0;
#else
	return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)(&ms), sizeof(ms)) == 0;
#endif
}

#define CONNECTION_ACTIVE			0
#define CONNECTION_OCCUPY			1

#ifndef XG_POLL_WAIT_TIMEOUT
#define XG_POLL_WAIT_TIMEOUT		100
#endif

#ifndef XG_SOCKSVR_MAXCONNSIZE
#define XG_SOCKSVR_MAXCONNSIZE		10000
#endif

typedef struct
{
#ifndef XG_LINUX
	WSAOVERLAPPED overlap;
	WSABUF buffer;
#endif
	int flag;
	time_t tm;
	SOCKET sock;
	stConnectData data;
} stConnectInfo;

static int timeout;
static HANDLE handle;
static MutexHandle mutex;
static stConnectInfo* connpool;

static void(*Lock)() = NULL;
static void(*Unlock)() = NULL;
static int(*ProcessRequest)(SOCKET, stConnectData*) = NULL;
static int(*ProcessConnectClosed)(SOCKET, stConnectData*) = NULL;
static int(*ProcessConnect)(SOCKET, stConnectData*, const char*, int) = NULL;

/////////////////////////////////////////////////////////////////////

static int PostRecv(stConnectInfo* conn);

static void MutexLock()
{
	LockMutex(mutex);
}
static void MutexUnlock()
{
	UnlockMutex(mutex);
}
void ServerSocketSetLockFunction(void(*lock)(), void(*unlock)())
{
	Lock = lock;
	Unlock = unlock;
}
void ServerSocketSetProcessFunction(int(*func)(SOCKET, stConnectData*))
{
	ProcessRequest = func;
}
void ServerSocketSetConnectClosedFunction(int(*func)(SOCKET, stConnectData*))
{
	ProcessConnectClosed = func;
}
void ServerSocketSetConnectFunction(int(*func)(SOCKET, stConnectData*, const char*, int))
{
	ProcessConnect = func;
}

/////////////////////////////////////////////////////////////////////

static void CloseConnect(stConnectInfo* conn)
{
	if (ProcessConnectClosed)
	{
		int res = ProcessConnectClosed(conn->sock, &conn->data);

		if (res == XG_DETACHCONN) conn->sock = INVALID_SOCKET;
	}

	if (conn->sock != INVALID_SOCKET)
	{
		SocketClose(conn->sock);
		conn->sock = INVALID_SOCKET;
	}
}
static stConnectInfo* MallocConnectNode(SOCKET sock, const char* address)
{
	static int len = 0;
	static time_t last = 0;
	static stConnectInfo* stack[XG_SOCKSVR_MAXCONNSIZE];

	time_t now = time(NULL);
	stConnectInfo* str = connpool;
	stConnectInfo* end = connpool + XG_SOCKSVR_MAXCONNSIZE;

	if (len == 0 || last + timeout < now)
	{
		last = now;
		len = 0;

		while (str < end)
		{
			if (str->sock == INVALID_SOCKET)
			{
				stack[len++] = str;
			}
			else
			{
				if (str->flag == CONNECTION_ACTIVE && str->tm + timeout < now)
				{
#ifdef XG_LINUX
					epoll_ctl(handle, EPOLL_CTL_DEL, str->sock, NULL);
#endif
					CloseConnect(str);
					stack[len++] = str;
				}
			}

			++str;
		}

		if (len == 0) return NULL;
	}

	str = stack[--len];
	strncpy(str->data.addr, address, sizeof(str->data.addr) - 1);
	str->data.addr[sizeof(str->data.addr) - 1] = 0;
	str->sock = sock;

	return str;
}
static void SocketServerInit(int second)
{
	int i = 0;

	timeout = second;

	if (Lock == NULL || Unlock == NULL)
	{
		InitMutex(mutex);

		Lock = MutexLock;
		Unlock = MutexUnlock;
	}

	if ((connpool = (stConnectInfo*)malloc(sizeof(stConnectInfo) * XG_SOCKSVR_MAXCONNSIZE)) == NULL) ErrorExit(XG_SYSERR);

	while (i < XG_SOCKSVR_MAXCONNSIZE) connpool[i++].sock = INVALID_SOCKET;
}
static void ProcessCommand(stConnectInfo* conn)
{
	int res = 0;

	conn->flag = CONNECTION_OCCUPY;

	if (ProcessRequest) res = ProcessRequest(conn->sock, &conn->data);

	if (res >= 0)
	{
		if (PostRecv(conn) < 0) CloseConnect(conn);
	}
	else if (res == XG_TIMEOUT)
	{
		time_t tm = conn->tm;

		if (PostRecv(conn) < 0) CloseConnect(conn);

		conn->tm = tm;
	}
	else if (res == XG_DETACHCONN)
	{
		conn->sock = INVALID_SOCKET;
	}
	else
	{
		CloseConnect(conn); 
	}
}
static void AcceptConnect(SOCKET sock, const char* host, int port)
{
	int res = 0;
	char address[32] = {0};
	stConnectInfo* conn = NULL;

	sock = ServerSocketAccept(sock, address);

	if (IsSocketClosed(sock)) return;
	
	Lock();
	
	if ((conn = MallocConnectNode(sock, address)) == NULL)
	{
		SocketClose(sock);
	}
#ifndef XG_LINUX
	else if (CreateIoCompletionPort((HANDLE)(sock), handle, 0, 0) == NULL)
	{
		CloseConnect(conn);
	}
#endif
	else if (ProcessConnect && (res = ProcessConnect(sock, &conn->data, host, port)) < 0)
	{
		if (res == XG_DETACHCONN)
		{
			conn->sock = INVALID_SOCKET;
		}
		else
		{
			CloseConnect(conn);
		}
	}
	else if (PostRecv(conn) < 0)
	{
		CloseConnect(conn);
	}
	
	Unlock();
}

#ifdef XG_LINUX

static int PostRecv(stConnectInfo* conn)
{
	struct epoll_event ev;

	ev.data.ptr = conn;
	ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;

	if (epoll_ctl(handle, EPOLL_CTL_ADD, conn->sock, &ev) < 0) return XG_SYSERR;

	conn->flag = CONNECTION_ACTIVE;
	conn->tm = time(NULL);

	return XG_OK;
}

static void MainProcess(void* data)
{
	int i = 0;
	int cnt = 0;
	stConnectInfo* conn = NULL;
	struct epoll_event* evs = (struct epoll_event*)malloc(XG_SOCKSVR_MAXCONNSIZE * sizeof(struct epoll_event));

	while (TRUE)
	{
		cnt = epoll_wait(handle, evs, XG_SOCKSVR_MAXCONNSIZE, XG_POLL_WAIT_TIMEOUT);

		for (i = 0; i < cnt; i++)
		{
			conn = (stConnectInfo*)(evs[i].data.ptr);

			epoll_ctl(handle, EPOLL_CTL_DEL, conn->sock, NULL);

			if (evs[i].events & EPOLLIN)
			{
				ProcessCommand(conn);
			}
			else
			{
				CloseConnect(conn);
			}
		}
	}
}

#else

static int PostRecv(stConnectInfo* conn)
{
	int res = 0;
	DWORD sz = 0;
	DWORD flag = 0;

	conn->buffer.len = 0;
	conn->buffer.buf = NULL;
	memset(&conn->overlap, 0, sizeof(conn->overlap));

	res = WSARecv(conn->sock, &conn->buffer, 1, &sz, &flag, &conn->overlap, NULL);

	if (res == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) return XG_SYSERR;

	conn->flag = CONNECTION_ACTIVE;
	conn->tm = time(NULL);

	return XG_OK;
}
static void MainProcess(void* data)
{
	DWORD sz = 0;
	ULONGLONG key = 0;
	stConnectInfo* conn = NULL;

	while (TRUE)
	{
		if (GetQueuedCompletionStatus(handle, &sz, (PULONG_PTR)(&key), (LPOVERLAPPED*)(&conn), XG_POLL_WAIT_TIMEOUT))
		{
			ProcessCommand(conn);			
		}
		else if (conn)
		{
			CloseConnect(conn);
		}

		conn = NULL;
	}
}

#endif

void ServerSocketLoop(const char* host, int port, int backlog, int timeout)
{
	static int inited = 0;

	if (inited == 0)
	{
		SocketServerInit(timeout);

#ifdef XG_LINUX
		handle = epoll_create(XG_SOCKSVR_MAXCONNSIZE);
#else
		handle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
#endif
		inited = StartThread(MainProcess, NULL);
	}

	SOCKET sock = CreateServerSocket(host, port, backlog);

	if (!IsSocketClosed(sock)) while (TRUE) AcceptConnect(sock, host, port);
}

BOOL ServerSocketAttach(SOCKET sock, const char* address)
{
	stConnectInfo* conn = NULL;

	Lock();

	if ((conn = MallocConnectNode(sock, address)) == NULL)
	{
		Unlock();

		return FALSE;
	}

	if (PostRecv(conn) < 0)
	{
		conn->sock = INVALID_SOCKET;

		Unlock();

		return FALSE;
	}

	Unlock();

	return TRUE;
}